Skip to content

Feature: Add Gemini and Z.ai (GLM) usage tracking. Improve task recovery and remove unused variables#773

Closed
gsxdsm wants to merge 6 commits intoAutoMaker-Org:v0.15.0rcfrom
gsxdsm:feat/add-zai-usage-tracking
Closed

Feature: Add Gemini and Z.ai (GLM) usage tracking. Improve task recovery and remove unused variables#773
gsxdsm wants to merge 6 commits intoAutoMaker-Org:v0.15.0rcfrom
gsxdsm:feat/add-zai-usage-tracking

Conversation

@gsxdsm
Copy link
Collaborator

@gsxdsm gsxdsm commented Feb 16, 2026

Add usage tracking for Gemini and Z.ai coding plan models. Improve task recovery and UI state sync. Remove unused variables.

Summary by CodeRabbit

  • New Features
    • Added z.ai and Gemini integrations: usage monitoring, status detection, and on-demand verification/configuration.
    • Usage UI updated: Usage popover, mobile/desktop bars, and board headers now show z.ai and Gemini alongside existing providers.
    • Settings: z.ai API key test/save flows and status reporting added.
    • Icons & links: provider icons and per-provider footers updated for z.ai and Gemini.

@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2026

📝 Walkthrough

Walkthrough

Adds z.ai and Gemini integrations across server and UI: new usage services, API routes (/api/zai, /api/gemini), UI hooks, store types/state, settings migration for zai API key, provider configuration UI, and usage display components. Also includes various typing, error-handling, and minor cleanup changes.

Changes

Cohort / File(s) Summary
Server: z.ai service & routes
apps/server/src/services/zai-usage-service.ts, apps/server/src/routes/zai/index.ts
New ZaiUsageService and router exposing /usage, /configure, /verify, /status; supports token/host management, verification, fetch/parse of quota responses, persistence via SettingsService.
Server: Gemini service & routes
apps/server/src/services/gemini-usage-service.ts, apps/server/src/routes/gemini/index.ts
New GeminiUsageService that extracts CLI OAuth credentials, queries quota/code-assist endpoints, token refresh/persistence; router exposes /usage and /status with tiered quota shapes and installation detection.
Server: startup & wiring
apps/server/src/index.ts, apps/server/src/services/settings-service.ts
Instantiate and mount zai and gemini routes; create ZaiUsageService; resume interrupted features after reconciliation; migrate settings to include zai apiKey.
UI: store, types, and auth state
apps/ui/src/store/*, apps/ui/src/store/types/*, libs/types/src/settings.ts, apps/ui/src/store/types/settings-types.ts
Adds Zai and Gemini usage types and responses; extends AppState/AppActions and SetupState/SetupActions with zai/gemini usage and auth status; initializes apiKeys.zai.
UI: hooks, queries, and API clients
apps/ui/src/hooks/queries/use-usage.ts, apps/ui/src/hooks/queries/index.ts, apps/ui/src/lib/query-keys.ts, apps/ui/src/lib/http-api-client.ts, apps/ui/src/lib/electron.ts
Adds useZaiUsage/useGeminiUsage hooks, query keys, and HttpApiClient/Electron API surfaces for zai and gemini (status/getUsage/configure/verify); includes mock implementations.
UI: settings & provider config
apps/ui/src/config/api-providers.ts, apps/ui/src/components/views/settings-view/api-keys/hooks/use-api-key-management.ts
Adds zai provider config entry and management flow: local state, test/verify, server-side configure, persistence and query invalidation.
UI: usage display and related components
apps/ui/src/components/usage-popover.tsx, apps/ui/src/components/views/board-view/mobile-usage-bar.tsx, apps/ui/src/components/views/board-view/board-header.tsx, apps/ui/src/components/views/board-view/header-mobile-menu.tsx
Extends UsagePopover, MobileUsageBar, and board header/mobile menu to show z.ai and Gemini tabs/cards, icons, reset/number formatting helpers, per-provider stale/refetch logic, and UI states for auth/errors.
UI: icons
apps/ui/src/components/ui/provider-icon.tsx
Adds ZaiIcon alias and sets explicit GLM/GLM-derived icon fill color.
Various: typing, error handling & small cleanups
many server and UI files (e.g., apps/server/src/lib/error-handler.ts, permission-enforcer, multiple catch blocks, imports cleanup)
Tightened types (Record<string, unknown>), adjusted several catch clauses, removed unused imports/vars, minor refactors and naming changes across many files.
Tests
apps/server/tests/unit/services/recovery-service.test.ts, apps/server/src/tests/cli-integration.test.ts
Adds unit tests for recovery/resume logic; small test import adjustments.

Sequence Diagram(s)

sequenceDiagram
    participant UI as UI Component
    participant Hook as useZaiUsage Hook
    participant Client as HttpApiClient
    participant Server as Server (/api/zai)
    participant Service as ZaiUsageService
    participant ZaiAPI as z.ai API

    UI->>Hook: mount / request refresh
    Hook->>Client: http.zai.getUsage()
    Client->>Server: GET /api/zai/usage
    Server->>Service: fetchUsageData()
    Service->>ZaiAPI: POST /quota (with token)
    ZaiAPI-->>Service: Quota response
    Service->>Server: ZaiUsageData
    Server-->>Client: ZaiUsageData
    Client-->>Hook: ZaiUsageData
    Hook->>UI: update store -> render usage
Loading
sequenceDiagram
    participant Hook as useProviderAuthInit
    participant Client as HttpApiClient
    participant Server as Server (/api/gemini)
    participant Service as GeminiUsageService
    participant CLI as Gemini CLI

    Hook->>Client: http.setup.getGeminiStatus()
    Client->>Server: GET /api/gemini/status
    Server->>Service: create provider & detectInstallation()
    Service->>CLI: locate binary & read oauth2.js
    CLI-->>Service: oauth credentials / version
    Service-->>Server: status payload (installed/authenticated/method)
    Server-->>Client: status
    Client-->>Hook: status
    Hook->>Store: setGeminiAuthStatus(status)
Loading

(Note: rectangles/colors not required for simple sequence flow.)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

Enhancement

🐰
I hopped along the code-lined trail,
Two new friends carried a quota tale,
Tokens and tiers in tidy rows,
Now apps and servers proudly know,
Usage shown — the warren glows. 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the primary changes: adding Gemini and Z.ai usage tracking, improving task recovery, and removing unused variables—all major themes reflected in the raw summary.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gsxdsm gsxdsm changed the base branch from main to v0.15.0rc February 16, 2026 05:23
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @gsxdsm, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly expands the platform's monitoring capabilities by integrating usage tracking for Gemini and Z.ai models, offering users better visibility into their coding plan consumption. It also refines the Claude model experience by upgrading to Opus 4.6 with adaptive thinking and enhances authentication diagnostics. Furthermore, the update includes support for the latest GPT-5.3-Codex model and streamlines the Docker environment for automated testing with Playwright.

Highlights

  • Gemini and Z.ai Usage Tracking: Introduced comprehensive usage tracking for Gemini and Z.ai (GLM) coding plan models, including new API routes, services, and UI components to display real-time quota and usage data.
  • Claude Opus 4.6 Integration with Adaptive Thinking: Updated the Claude Opus model to version 4.6, which now supports 'adaptive thinking'. This feature allows the model to dynamically determine its reasoning depth, removing the need for a fixed token budget for thinking. The UI has been updated to reflect this new thinking level option.
  • Enhanced Claude CLI Authentication Logging: Improved the Claude CLI authentication process by adding detailed logging and better detection of OAuth tokens versus API keys, providing more clarity during setup and credential checks.
  • New GPT-5.3-Codex Model: Added support for the new GPT-5.3-Codex model, updating model definitions and descriptions across the application to reflect its status as the latest frontier agentic coding model.
  • Playwright Chromium Pre-installation in Docker: Integrated Playwright Chromium directly into the Docker image, enabling automated AI agent verification tests out-of-the-box without additional setup. Documentation has been updated to guide users on persisting browser installations.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • .gitignore
    • Added new entries for '.mcp.json' and '.planning' to ignore files generated by the system.
  • CLAUDE.md
    • Updated the Claude Opus model version from 'claude-opus-4-5-20251101' to 'claude-opus-4-6'.
  • Dockerfile
    • Added a blank line after the OpenCode CLI installation command for formatting.
    • Installed Playwright Chromium browser for AI agent verification tests, adding approximately 300MB to the image size.
  • README.md
    • Added a new section detailing Playwright Chromium pre-installation in the Docker image for automated testing.
    • Included instructions for optionally persisting Playwright browsers using a volume mount and manually updating them.
  • apps/server/package.json
    • Updated the @anthropic-ai/claude-agent-sdk dependency from 0.1.76 to 0.2.32.
    • Updated the @openai/codex-sdk dependency from 0.77.0 to 0.98.0.
    • Added @playwright/test version 1.57.0 as a dev dependency.
  • apps/server/src/index.ts
    • Imported createZaiRoutes, ZaiUsageService, and createGeminiRoutes.
    • Initialized zaiUsageService.
    • Registered new API routes for /api/zai and /api/gemini.
  • apps/server/src/lib/sdk-options.ts
    • Updated the buildThinkingOptions function to support 'adaptive' thinking for Claude Opus 4.6, omitting maxThinkingTokens when adaptive thinking is selected.
  • apps/server/src/providers/claude-provider.ts
    • Updated the Claude Opus model ID from 'claude-opus-4-5-20251101' to 'claude-opus-4-6' in getAvailableModels.
    • Adjusted the maxOutputTokens for Claude Opus 4.6 from 16000 to 128000.
    • Modified the description for Claude Opus 4.6 to highlight adaptive thinking.
    • Updated the maxThinkingTokens logic to set it to undefined for 'adaptive' thinking levels.
  • apps/server/src/providers/codex-models.ts
    • Added a new GPT-5.3-Codex model definition.
    • Updated the description for GPT-5.2-Codex and GPT-5.1-Codex-Max models.
    • Modified the description for GPT-5.1-Codex-Mini and GPT-5.2 models.
  • apps/server/src/providers/provider-factory.ts
    • Updated the example model ID in the JSDoc for getProviderForModel to 'claude-opus-4-6'.
  • apps/server/src/routes/gemini/index.ts
    • Added a new file to define API routes for Gemini usage and status, including endpoints for fetching usage data and checking CLI installation/authentication status.
  • apps/server/src/routes/setup/routes/verify-claude-auth.ts
    • Imported getClaudeAuthIndicators from @automaker/platform.
    • Enhanced the Claude authentication verification logic to determine and return a specific authType ('oauth', 'api_key', or 'cli').
  • apps/server/src/routes/zai/index.ts
    • Added a new file to define API routes for Z.ai usage, configuration, and verification, including endpoints for fetching usage data, setting API tokens, and testing API key validity.
  • apps/server/src/services/gemini-usage-service.ts
    • Added a new file to implement the GeminiUsageService, responsible for tracking Gemini CLI usage and quota by interacting with Google Cloud's internal quota API and extracting OAuth credentials.
  • apps/server/src/services/settings-service.ts
    • Added a 'zai' property to the apiKeys object within the credentials migration logic, initializing it as an empty string.
  • apps/server/src/services/zai-usage-service.ts
    • Added a new file to implement the ZaiUsageService, which fetches usage quota data from the z.ai API, handles API token authentication, and parses various API response formats.
  • apps/server/tests/unit/lib/model-resolver.test.ts
    • Updated expected model string for 'opus' alias to 'claude-opus-4-6' in unit tests.
  • apps/server/tests/unit/lib/sdk-options.test.ts
    • Added new test cases for 'adaptive' thinking level, ensuring maxThinkingTokens is not set for adaptive thinking in createAutoModeOptions.
  • apps/server/tests/unit/providers/claude-provider.test.ts
    • Updated all references to the Claude Opus model ID from 'claude-opus-4-5-20251101' to 'claude-opus-4-6' in unit tests.
  • apps/server/tests/unit/providers/provider-factory.test.ts
    • Updated expected model ID for ClaudeProvider tests to 'claude-opus-4-6'.
  • apps/ui/docs/AGENT_ARCHITECTURE.md
    • Updated the example model in the agent configuration snippet from 'claude-opus-4-5-20251101' to 'claude-opus-4-6'.
  • apps/ui/src/components/dialogs/sandbox-risk-dialog.tsx
    • Added a new section with troubleshooting steps for users running Automaker in Docker, including checks for IS_CONTAINERIZED and server logs.
  • apps/ui/src/components/ui/provider-icon.tsx
    • Added a fill color (#3B82F6) to the GLM icon definition.
    • Exported ZaiIcon as an alias of GlmIcon.
  • apps/ui/src/components/usage-popover.tsx
    • Imported ZaiIcon and GeminiIcon.
    • Imported useZaiUsage and useGeminiUsage hooks.
    • Modified formatCodexResetTime to be a general formatResetTime and added a legacy alias.
    • Added a formatNumber helper function for large numbers.
    • Integrated Z.ai and Gemini usage data fetching and display into the usage popover, including new tabs and usage cards.
    • Adjusted tab visibility logic to include Z.ai and Gemini.
  • apps/ui/src/components/views/board-view/board-header.tsx
    • Added zaiAuthStatus and geminiAuthStatus to the component state.
    • Implemented logic to determine showZaiUsage and showGeminiUsage based on authentication status.
    • Updated the UsagePopover and HeaderMobileMenu components to conditionally display based on Z.ai and Gemini usage visibility.
  • apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx
    • Imported isAdaptiveThinkingModel from @automaker/types.
    • Modified handleModelChange to normalize thinking levels when switching between adaptive and non-adaptive models (e.g., auto-switching to 'adaptive' for Opus 4.6 or 'high' when switching away).
  • apps/ui/src/components/views/board-view/header-mobile-menu.tsx
    • Added showZaiUsage and showGeminiUsage props.
    • Updated conditional rendering for MobileUsageBar to include Z.ai and Gemini usage.
  • apps/ui/src/components/views/board-view/mobile-usage-bar.tsx
    • Imported ZaiIcon and GeminiIcon.
    • Added showZaiUsage and showGeminiUsage props.
    • Implemented formatNumber and formatResetTime helper functions.
    • Added state for Z.ai and Gemini usage, loading, and last updated times.
    • Integrated fetching and display of Z.ai and Gemini usage data, including usage bars for tokens, MCP, Flash, and Pro quotas.
  • apps/ui/src/components/views/board-view/shared/model-constants.ts
    • Added 'adaptive' to THINKING_LEVELS and THINKING_LEVEL_LABELS.
  • apps/ui/src/components/views/board-view/shared/thinking-level-selector.tsx
    • Imported getThinkingLevelsForModel from @automaker/types.
    • Modified the component to filter available thinking levels based on the selected model, specifically for adaptive thinking models.
    • Updated the descriptive text to explain adaptive thinking.
  • apps/ui/src/components/views/settings-view/api-keys/hooks/use-api-key-management.ts
    • Imported useQueryClient, useSetupStore, getHttpApiClient, and queryKeys.
    • Added hasZaiKey to ApiKeyStatus interface.
    • Added state for zaiKey, showZaiKey, testingZaiConnection, and zaiTestResult.
    • Implemented handleTestZaiConnection to verify Z.ai API keys.
    • Modified handleSave to persist zaiKey and configure the Z.ai service on the server, including updating Z.ai auth status and invalidating usage queries.
  • apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx
    • Imported getThinkingLevelsForModel from @automaker/types.
    • Updated the thinking level options to dynamically show 'adaptive' for appropriate models and provide a description for it.
  • apps/ui/src/components/views/settings-view/providers/codex-model-configuration.tsx
    • Imported supportsReasoningEffort from @automaker/types.
    • Added codex-gpt-5.3-codex to CODEX_MODEL_INFO.
    • Updated descriptions for existing Codex models.
  • apps/ui/src/components/views/setup-view/steps/claude-setup-step.tsx
    • Added cliAuthType state to track the specific type of Claude CLI authentication.
    • Modified verifyCliAuth to store authType and display specific success messages for Claude Code subscription (OAuth) vs. generic CLI authentication.
  • apps/ui/src/config/api-providers.ts
    • Added 'zai' to ProviderKey type.
    • Included Z.ai API key configuration in buildProviderConfigs, defining its label, input fields, test button, and description.
  • apps/ui/src/hooks/queries/index.ts
    • Exported useZaiUsage and useGeminiUsage from ./use-usage.
  • apps/ui/src/hooks/queries/use-usage.ts
    • Updated JSDoc to include z.ai and Gemini API usage data.
    • Modified error messages for Claude and Codex API unavailability to specify 'API bridge unavailable'.
    • Added useZaiUsage hook to fetch z.ai API usage data with polling.
    • Added useGeminiUsage hook to fetch Gemini API usage/status data with polling.
  • apps/ui/src/hooks/use-provider-auth-init.ts
    • Added setZaiAuthStatus, setGeminiCliStatus, setGeminiAuthStatus, zaiAuthStatus, and geminiAuthStatus to the component state.
    • Extended refreshStatuses to initialize Z.ai and Gemini authentication statuses on app startup.
    • Updated the useEffect dependency array to include new Z.ai and Gemini auth statuses.
  • apps/ui/src/lib/agent-context-parser.ts
    • Updated DEFAULT_MODEL to 'claude-opus-4-6'.
    • Modified formatModelName to display 'Opus 4.6' and 'GPT-5.3 Codex' correctly.
  • apps/ui/src/lib/electron.ts
    • Imported new usage response types for Z.ai and Gemini.
    • Added zai and gemini properties to the ElectronAPI interface, defining their respective API methods.
    • Implemented mock data for zai and gemini API calls in _getMockElectronAPI.
    • Added authType to the return type of verifyClaudeAuth in SetupAPI.
  • apps/ui/src/lib/http-api-client.ts
    • Imported GeminiUsage type.
    • Added authType to the return type of verifyClaudeAuth.
    • Implemented zai API client methods for status, usage, configure, and verify.
    • Implemented gemini API client method for getUsage.
  • apps/ui/src/lib/query-keys.ts
    • Added new query keys for zai and gemini API usage.
  • apps/ui/src/store/app-store.ts
    • Imported new types for Z.ai and Gemini usage.
    • Added zai to the initial apiKeys state.
    • Added zaiUsage and zaiUsageLastUpdated to the initial AppState.
    • Added setZaiUsage action to update Z.ai usage state.
  • apps/ui/src/store/setup-store.ts
    • Defined new types ZaiAuthMethod, ZaiAuthStatus, GeminiAuthMethod, and GeminiAuthStatus.
    • Added geminiAuthStatus and zaiAuthStatus to SetupState.
    • Added setGeminiAuthStatus and setZaiAuthStatus actions to SetupActions.
  • apps/ui/src/store/types/settings-types.ts
    • Added zai property to the ApiKeys interface.
  • apps/ui/src/store/types/state-types.ts
    • Imported ZaiUsage type.
    • Added zaiUsage and zaiUsageLastUpdated properties to AppState.
    • Added setZaiUsage to AppActions.
  • apps/ui/src/store/types/usage-types.ts
    • Defined new types for Z.ai usage (ZaiPlanType, ZaiQuotaLimit, ZaiUsage, ZaiUsageResponse).
    • Defined new types for Gemini usage (GeminiQuotaBucket, GeminiTierQuota, GeminiUsage, GeminiUsageResponse).
  • docker-compose.override.yml.example
    • Updated comments for the Playwright browser cache section, clarifying pre-installation and manual update instructions.
  • docs/llm-shared-packages.md
    • Updated the Claude Opus model version from 'claude-opus-4-5-20251101' to 'claude-opus-4-6'.
  • docs/server/providers.md
    • Updated Claude Opus model references from 'claude-opus-4-5-20251101' to 'claude-opus-4-6' in documentation examples.
  • docs/server/utilities.md
    • Updated Claude Opus model references from 'claude-opus-4-5-20251101' to 'claude-opus-4-6' in documentation examples.
  • libs/model-resolver/README.md
    • Updated Claude Opus model references from 'claude-opus-4-5-20251101' to 'claude-opus-4-6' in README examples.
  • libs/model-resolver/tests/resolver.test.ts
    • Updated Claude Opus model references from 'claude-opus-4-5-20251101' to 'claude-opus-4-6' in unit tests.
  • libs/platform/src/index.ts
    • Exported FileCheckResult and DirectoryCheckResult types.
  • libs/platform/src/system-paths.ts
    • Added getNvmWindowsCliPaths helper function to include NVM for Windows symlink paths for CLI tools.
    • Integrated getNvmWindowsCliPaths into getClaudeCliPaths, getCodexCliPaths, and getOpenCodeCliPaths.
    • Defined FileCheckResult and DirectoryCheckResult interfaces.
    • Enhanced getClaudeAuthIndicators to provide detailed checks results for settings file, stats cache, projects directory, and credential files, including existence, readability, and error information.
  • libs/platform/tests/oauth-credential-detection.test.ts
    • Added a new file containing comprehensive unit tests for OAuth credential detection scenarios, covering various Claude, Codex, and OpenCode authentication file formats, edge cases, and combined setups.
  • libs/types/src/codex-models.ts
    • Added codex-gpt-5.3-codex to CodexModelId type.
    • Included codex-gpt-5.3-codex in CODEX_MODEL_CONFIG_MAP with its description and capabilities.
    • Updated descriptions for existing Codex models like gpt52Codex, gpt51CodexMax, gpt51CodexMini, and gpt52.
  • libs/types/src/event.ts
    • Added dev-server:url-detected to EventType.
  • libs/types/src/index.ts
    • Exported isAdaptiveThinkingModel and getThinkingLevelsForModel.
  • libs/types/src/model-display.ts
    • Added codex-gpt-5.3-codex to CODEX_MODELS.
    • Updated descriptions for existing Codex models.
    • Added 'adaptive' to THINKING_LEVELS and THINKING_LEVEL_LABELS.
  • libs/types/src/model.ts
    • Updated CLAUDE_CANONICAL_MAP and CLAUDE_MODEL_MAP to reference 'claude-opus-4-6' instead of 'claude-opus-4-5-20251101'.
    • Added gpt53Codex to CODEX_MODEL_MAP and REASONING_CAPABLE_MODELS.
    • Updated descriptions for various Codex models in CODEX_MODEL_MAP.
  • libs/types/src/settings.ts
    • Added 'adaptive' to ThinkingLevel type.
    • Included adaptive: undefined in THINKING_TOKEN_BUDGET.
    • Added isAdaptiveThinkingModel and getThinkingLevelsForModel functions.
    • Added zai property to ApiKeys and Credentials interfaces, and to DEFAULT_CREDENTIALS.
  • package-lock.json
    • Updated @anthropic-ai/claude-agent-sdk to 0.2.32.
    • Updated @openai/codex-sdk to 0.98.0.
    • Adjusted zod peer dependency version for @anthropic-ai/claude-agent-sdk.
    • Removed peer: true from several @playwright/browser-chromium entries.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds usage tracking for Gemini and Z.ai models, including backend services, API routes, and UI components. The changes are extensive and well-structured. I've identified a few areas for improvement, mainly concerning error handling, performance in synchronous calls, and code duplication in the new backend services. Additionally, there's a minor regression in the UI that should be addressed. Overall, this is a solid feature addition.

private findGeminiBinaryPath(): string | null {
try {
// Try 'which' on Unix-like systems
const whichResult = execSync('which gemini 2>/dev/null', { encoding: 'utf8' }).trim();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The use of execSync is synchronous and blocks the Node.js event loop, which can degrade server performance. While extractOAuthClientCredentials caches the result, the first call to this endpoint will block. This happens again on line 559. It is recommended to use the asynchronous exec from child_process and make the calling functions (findGeminiBinaryPath and extractOAuthClientCredentials) async to avoid blocking.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/server/src/services/settings-service.ts (1)

728-754: ⚠️ Potential issue | 🟠 Major

getMaskedCredentials() does not include the new zai key.

The method returns masked credentials for anthropic, google, and openai but omits the newly added zai key. This means the UI cannot display whether a z.ai API key is configured.

🔧 Proposed fix
   async getMaskedCredentials(): Promise<{
     anthropic: { configured: boolean; masked: string };
     google: { configured: boolean; masked: string };
     openai: { configured: boolean; masked: string };
+    zai: { configured: boolean; masked: string };
   }> {
     const credentials = await this.getCredentials();
 
     const maskKey = (key: string): string => {
       if (!key || key.length < 8) return '';
       return `${key.substring(0, 4)}...${key.substring(key.length - 4)}`;
     };
 
     return {
       anthropic: {
         configured: !!credentials.apiKeys.anthropic,
         masked: maskKey(credentials.apiKeys.anthropic),
       },
       google: {
         configured: !!credentials.apiKeys.google,
         masked: maskKey(credentials.apiKeys.google),
       },
       openai: {
         configured: !!credentials.apiKeys.openai,
         masked: maskKey(credentials.apiKeys.openai),
       },
+      zai: {
+        configured: !!credentials.apiKeys.zai,
+        masked: maskKey(credentials.apiKeys.zai),
+      },
     };
   }
apps/ui/src/components/usage-popover.tsx (1)

30-46: ⚠️ Potential issue | 🟡 Minor

Guard against past reset times in formatResetTime.

If the reset time is already in the past, the current logic yields negative “Resets in -Xm” text. Add a clamp or fallback message.

🧹 Suggested fix
   const now = new Date();
   const diff = date.getTime() - now.getTime();
+  if (diff <= 0) {
+    return 'Resetting soon';
+  }
🤖 Fix all issues with AI agents
In `@apps/server/src/routes/gemini/index.ts`:
- Around line 35-57: Remove the unsafe (status as any) casts: use the typed
InstallationStatus returned by GeminiProvider.detectInstallation() and compute
authMethod using the existing fields (status.authenticated / status.hasApiKey)
without casting; also remove or type-augment hasCredentialsFile—either add
hasCredentialsFile to the InstallationStatus interface (and populate it in
detectInstallation) or stop returning hasCredentialsFile and default it to false
in the JSON response; update the route in apps/server/src/routes/gemini/index.ts
(references: GeminiProvider, detectInstallation, authMethod, hasCredentialsFile,
InstallationStatus) accordingly.

In `@apps/server/src/routes/zai/index.ts`:
- Around line 63-155: The route handlers for router.post('/configure') and
router.post('/verify') contain business logic
(usageService.setApiToken/setApiHost, settingsService.updateCredentials, fetch
to quota URL and response handling) and must be moved into ZaiUsageService (or a
new service) as methods like configure({apiToken, apiHost}) and
verifyApiKey(apiKey); those service methods should perform the persistence (call
settingsService.updateCredentials), runtime updates (usageService calls),
external fetch, and emit events via the shared emitter from createEventEmitter()
(e.g., emit 'zai.configured' and 'zai.verify.result' with status and error
info). Update the route handlers to simply call ZaiUsageService.configure(...)
and ZaiUsageService.verifyApiKey(...), await results, and return minimal JSON
responses, handling exceptions only to map errors to HTTP responses. Ensure you
reference and replace direct calls to usageService and
settingsService.updateCredentials inside the service and add emitter.emit calls
for UI updates.
- Around line 120-147: The z.ai verification fetch call can hang indefinitely;
update the fetch invocation in the route handler that calls quotaUrl to pass a
signal created via AbortSignal.timeout(timeoutMs) (e.g., const controllerSignal
= AbortSignal.timeout(5000)) in the fetch options so the request auto-aborts
after a reasonable timeout, and enhance the catch block to handle aborts (detect
AbortError / error.name === 'AbortError' and return a clear timeout JSON
response). Ensure you reference the existing fetch call that sets headers
Authorization/Accept and the surrounding try/catch so you only add the signal
option and the abort-specific error branch.

In `@apps/server/src/services/gemini-usage-service.ts`:
- Around line 688-697: The refreshed token is always written to
this.credentialsPath (e.g., in the getValidAccessToken flow) even when
credentials were loaded from a different file (such as
~/.config/gemini/oauth_creds.json), causing divergence; modify the
credential-loading logic (where cachedCredentials is set) to record the actual
source path into a new property (e.g., this.loadedCredentialsPath) and then
change the write path in the save/refresh block to use that recorded
this.loadedCredentialsPath (falling back to this.credentialsPath if not set) so
refreshed tokens are persisted back to the same file they were read from.
- Around line 349-353: The possiblePaths array contains a duplicate because the
constructor already sets this.credentialsPath to the home
'.gemini/oauth_creds.json' path; remove the redundant literal path from
possiblePaths or dedupe the array before use (e.g., filter out duplicates by
keeping only the first occurrence) so possiblePaths only contains unique
entries; update references around possiblePaths and ensure the logic using
possiblePaths still checks the same fallback locations in
GeminiUsageService/constructor.
- Around line 343-384: The cachedCredentials in loadCredentials() is never
invalidated so stale/expired creds persist; modify loadCredentials (and/or
clearCache()) to implement automatic invalidation by adding a TTL (e.g., 5
minutes) or validating the credentials file mtime before returning
cachedCredentials: store a cachedAt timestamp alongside cachedCredentials when
setting it in loadCredentials(), and when loadCredentials is called check if
cachedAt + TTL has expired or if fs.statSync(credPath).mtimeMs is newer than
cachedAt — if expired or file changed, discard cachedCredentials and re-read
from disk (then update cachedAt); ensure clearCache() still exists to allow
explicit invalidation and reference credentialsPath/possiblePaths when comparing
mtimes.
- Around line 557-578: The fallback search in extractOAuthClientCredentials uses
execSync with an interpolated baseDir, creating a command-injection and
quoting/portability risk; change it to only run on POSIX (guard with
process.platform !== 'win32') and replace the string-shell execSync call with a
safe execFileSync/spawnSync invocation that passes 'find' and its arguments as
an array (e.g., args: [ `${baseDir}/..`, '-name', 'oauth2.js', '-path',
'*gemini*', '-path', '*code_assist*' ]) to avoid shell interpolation, keep the
same encoding/timeout options, then proceed to read and pass the result to
parseOAuthCredentialsFromSource and set this.cachedClientCredentials as before.
- Around line 168-202: The external fetch calls in fetchUsageData
(CODE_ASSIST_URL, QUOTA_API_URL, GOOGLE_TOKEN_URL) lack timeouts and can hang;
add an AbortSignal with a timeout for each request (e.g., use
AbortSignal.timeout(timeoutMs) or new AbortController().signal with setTimeout
to abort) and pass the signal option into fetch, catch the resulting
abort/timeout errors and handle/log them similarly to other fetch failures;
ensure each fetch (the code block around CODE_ASSIST_URL, the QUOTA_API_URL
POST, and the GOOGLE_TOKEN_URL call) is updated to create/use a signal and clean
up any controllers if you use AbortController.

In `@apps/server/src/services/zai-usage-service.ts`:
- Around line 165-176: The fetchUsageData method does not emit lifecycle events
for frontend streaming; modify fetchUsageData to call createEventEmitter()
(import from lib/events.ts) at start and emit a "zai.usage.start" event, then
emit "zai.usage.success" with the fetched ZaiUsageData on successful completion,
and emit "zai.usage.error" with error details if any exception occurs (rethrow
after emitting). Use the existing method name fetchUsageData and the same logger
for messages, ensure the emitter is created inside the method and closed/handled
per createEventEmitter's contract so events stream via WebSocket to the
frontend.
- Around line 182-190: The fetch call to quotaUrl currently has no timeout; wrap
it with an AbortController and a timeout like other services (e.g.,
verify-claude-auth.ts/event-hook-service.ts): create an AbortController, start a
setTimeout to call controller.abort() after the configured timeout, pass
controller.signal into the fetch options (where Authorization and Accept headers
are set), and clear the timeout after fetch completes; also handle the abort
error case so the calling function (the code around quotaUrl/token/response)
throws or returns a proper timeout error instead of hanging.

In `@apps/ui/src/components/views/board-view/mobile-usage-bar.tsx`:
- Around line 38-54: The formatResetTime function returns negative "Resets in
-Xm" for past timestamps; update formatResetTime(unixTimestamp, isMilliseconds)
to detect diff <= 0 and return a short message like "Resetting soon" (matching
server-side behavior) before computing minutes/hours/dates, keeping the existing
branches for <1h, <24h and fallback to date; touch the function name
formatResetTime and ensure the diff calculation (const diff = date.getTime() -
now.getTime()) is used to decide the early-return for past/resetting-soon cases.
- Around line 166-167: Add geminiUsage and geminiUsageLastUpdated to the
existing Zustand store alongside the other provider states (the same store that
exposes setClaudeUsage/setCodexUsage/setZaiUsage): add state fields geminiUsage:
GeminiUsage | null and geminiUsageLastUpdated: number | null, and a setter
setGeminiUsage((usage, lastUpdated?) => void) that updates both fields. In
mobile-usage-bar.tsx remove the local useState declarations for geminiUsage and
geminiUsageLastUpdated and replace them with the store selectors/hooks (e.g.,
const { geminiUsage, geminiUsageLastUpdated, setGeminiUsage } = useStore(...))
and update any places that call setGeminiUsage to use the store setter. Ensure
types match the existing GeminiUsage type and that other components (like
UsagePopover) can read the new store fields.

In
`@apps/ui/src/components/views/settings-view/api-keys/hooks/use-api-key-management.ts`:
- Around line 239-259: The code persists the z.ai key to the local store via
setApiKeys before calling api.zai.configure, and uses a loose check
(result.success || result.isAvailable) so the local state can show "Saved" even
if server config failed; either move the setApiKeys call so it runs only after
api.zai.configure succeeds (and then update setZaiAuthStatus and call
queryClient.invalidateQueries), or implement a rollback in the catch where you
remove the stored zai key using the same setApiKeys call; also tighten the
success condition to require result.success (not just isAvailable) when deciding
success for configuring z.ai.
- Around line 246-252: The code in use-api-key-management.ts hardcodes
hasEnvApiKey: false when calling setZaiAuthStatus after saving a UI key, which
can incorrectly clear an existing env-configured key; update the call in the
block that calls setZaiAuthStatus to preserve the current env key flag by
reading the existing apiKeyStatus (e.g., use apiKeyStatus.hasEnvApiKey) and set
hasEnvApiKey to that value instead of false so you only update the fields that
truly change (authenticated, method, hasApiKey) while keeping hasEnvApiKey in
sync.
- Line 1: Remove the top-level "// `@ts-nocheck`" and add explicit TypeScript
typings for the hook and its internal entities: declare interfaces/types such as
ApiKey (id, name, key, createdAt, revoked?), ApiKeyState (array + validation
errors), AuthState (isAuthenticated, userId, token), and ServerConfig
(allowedOrigins, keyPolicy) used by the useApiKeyManagement hook; update
function signatures (useApiKeyManagement, loadApiKeys, saveApiKeys,
validateApiKey, revokeApiKey) and React state hooks to use these types, type any
localStorage/parsing and async calls with proper unions/Optionals, and fix
resulting type errors rather than silencing them so the file compiles with full
type safety.

In `@apps/ui/src/hooks/use-provider-auth-init.ts`:
- Around line 133-138: The code is assigning the wrong field to the CLI path:
update the call to setGeminiCliStatus so the path uses result.path (the actual
filesystem path) instead of result.status; locate the block where
setGeminiCliStatus is called (in use-provider-auth-init.ts) and replace the
path: result.status assignment with path: result.path while keeping installed:
result.installed ?? false and version: result.version unchanged.
- Around line 107-124: The z.ai auth response handler (api.zai.getStatus) can
leave authenticated/hasApiKey undefined; update the logic in the try block that
calls setZaiAuthStatus so it coerces result.available, result.hasApiKey, and
result.hasEnvApiKey to booleans (e.g., using !! or ?? false) and derive method
using those boolean values, and also add a fallback setZaiAuthStatus in the
non-success/exception path to set authenticated: false, method: 'none',
hasApiKey: false, hasEnvApiKey: false; reference setZaiAuthStatus,
api.zai.getStatus, and ZaiAuthMethod when making the changes.
🧹 Nitpick comments (6)
apps/ui/src/store/app-store.ts (1)

97-105: Consider hoisting Zai/Gemini usage types into @automaker/types.

These usage shapes now exist in both server services and the UI store; centralizing them avoids drift and simplifies cross-layer contracts.
Based on learnings, “Core TypeScript definitions with no dependencies should be isolated in the automaker/types package”.

Also applies to: 184-191

apps/ui/src/store/types/usage-types.ts (3)

65-66: limitType: 'TOKENS_LIMIT' | 'TIME_LIMIT' | string — the literal union members are redundant.

Since string is a supertype of all string literals, 'TOKENS_LIMIT' | 'TIME_LIMIT' | string collapses to just string at the type level. If the intent is to provide autocomplete hints while still allowing arbitrary strings, use the (string & {}) idiom instead.

✏️ Suggested fix
 export interface ZaiQuotaLimit {
-  limitType: 'TOKENS_LIMIT' | 'TIME_LIMIT' | string;
+  limitType: 'TOKENS_LIMIT' | 'TIME_LIMIT' | (string & {});
   limit: number;

108-133: authMethod is typed as string but documented with known values — prefer a string literal union.

The JSDoc on Line 111 lists 'cli_login' | 'api_key' | 'api_key_env' | 'none' but the actual type is string. This weakens type safety for consumers (e.g., the ternary chains in mobile-usage-bar.tsx Lines 407-411 comparing against literal strings). Using a union (with a (string & {}) escape hatch if needed) would catch typos at compile time.

✏️ Suggested fix
+export type GeminiAuthMethod = 'cli_login' | 'api_key' | 'api_key_env' | 'none';
+
 export interface GeminiUsage {
   /** Whether the user is authenticated (via CLI or API key) */
   authenticated: boolean;
-  /** Authentication method: 'cli_login' | 'api_key' | 'api_key_env' | 'none' */
-  authMethod: string;
+  /** Authentication method */
+  authMethod: GeminiAuthMethod | (string & {});

62-84: Move API response types to @automaker/types and standardize type definitions.

ZaiPlanType, ZaiQuotaLimit, ZaiUsage, GeminiQuotaBucket, GeminiTierQuota, and GeminiUsage are duplicated across frontend (apps/ui/src/store/types/usage-types.ts) and backend services (apps/server/src/services/). Since these are core type definitions with no app-specific dependencies, they should be defined once in @automaker/types and imported by both frontend and backend.

Additionally, align type safety for GeminiUsage.authMethod: use a literal union type ('cli_login' | 'api_key' | 'none') instead of string to match the backend definition and prevent type mismatches. The JSDoc mentions 'api_key_env', which is not present in the backend's type—clarify whether this is needed or remove it.

apps/ui/src/components/views/board-view/mobile-usage-bar.tsx (2)

375-425: Gemini usage rendering handles multiple states clearly.

The nested ternary covers: authenticated with tier quotas → authenticated without quotas (shows auth method + error/no-usage) → not authenticated → loading. The logic is sound, though the deeply nested ternaries could be extracted to a sub-component for readability in a follow-up.


24-54: Extract formatNumber and formatResetTime to a shared utility module.

These helpers are duplicated identically in usage-popover.tsx and mobile-usage-bar.tsx. Extract them to @/lib/format-utils.ts to eliminate duplication and improve maintainability across desktop (UsagePopover) and mobile (MobileUsageBar) usage-display components.

Note: codex-usage-popover.tsx has a separate, simpler formatResetTime implementation for Codex-specific formatting that should remain separate.

…sage-tracking

# Conflicts:
#	apps/ui/src/components/usage-popover.tsx
#	apps/ui/src/components/views/board-view/mobile-usage-bar.tsx
@gsxdsm gsxdsm changed the title Feature: Add Gemini and Z.ai (GLM) usage tracking Feature: Add Gemini and Z.ai (GLM) usage tracking. Improve task recovery and remove unused varianles Feb 17, 2026
@gsxdsm gsxdsm changed the title Feature: Add Gemini and Z.ai (GLM) usage tracking. Improve task recovery and remove unused varianles Feature: Add Gemini and Z.ai (GLM) usage tracking. Improve task recovery and remove unused variables Feb 17, 2026
@gsxdsm gsxdsm added Testers-Requested Request for others to test an enhancement or bug fix/etc. Refactor A complete logic rewrite is requested or being performed for an issue. Do Not Merge Use this label if something should not be merged. cleanup remove unused files in the codebase labels Feb 17, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
apps/server/src/routes/models/routes/providers.ts (1)

15-27: ⚠️ Potential issue | 🟠 Major

Add missing provider entries to the providers response map.

The endpoint fetches statuses for all registered providers (claude, cursor, codex, opencode, gemini, copilot) via checkAllProviders(), but only returns anthropic and cursor to the frontend. This means gemini and other providers cannot be discovered via the /providers endpoint. Either expand the response to include all fetched providers or document why certain providers are intentionally excluded.

Note: Z.ai is not in the provider registry and has a separate route, so it's not affected.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/routes/models/routes/providers.ts` around lines 15 - 27, The
providers response currently only returns "anthropic" and "cursor" from the
statuses map; update the providers Record in
apps/server/src/routes/models/routes/providers.ts (the providers variable
created from checkAllProviders()'s statuses) to include all registered providers
returned by checkAllProviders() — e.g., claude, cursor, codex, opencode, gemini,
copilot (and any others your registry returns) with appropriate fields
(available, version, path, method, authenticated and hasApiKey where relevant);
also correct the mapping for "anthropic" if it mistakenly reads statuses.claude
(ensure anthropic reads statuses.anthropic) so the frontend can discover all
providers via the /providers endpoint.
apps/server/src/routes/app-spec/common.ts (1)

50-55: ⚠️ Potential issue | 🟡 Minor

Pre-existing logic bug: if (running) on a RunningGeneration object is always truthy.

running is typed as RunningGeneration (an object). The truthiness check never looks at running.isRunning, so the function returns the first entry in the map rather than the first running entry. It works today only because setRunningState deletes entries on running=false, keeping the map as an implicit "all running" set — but that's a fragile invariant.

🐛 Proposed fix
 export function getRunningProjectPath(): string | null {
   for (const [path, running] of runningProjects.entries()) {
-    if (running) return path;
+    if (running.isRunning) return path;
   }
   return null;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/routes/app-spec/common.ts` around lines 50 - 55,
getRunningProjectPath currently checks the truthiness of the map value
(RunningGeneration) so it always returns the first map entry; update it to
inspect the actual running flag (e.g., running.isRunning) when iterating
runningProjects so it only returns the path for entries where
RunningGeneration.isRunning is true; ensure you reference the RunningGeneration
shape and preserve behavior of setRunningState (which may delete entries) so
this becomes a correct explicit check rather than relying on that invariant.
apps/server/src/providers/codex-provider.ts (1)

769-812: ⚠️ Potential issue | 🟠 Major

buildConfigOverrides(overrides) return value is discarded — CLI config settings are never applied.

buildConfigOverrides is a pure function that returns string[] of --config key=value flags. The returned array is not stored or spread into args, so none of the built overrides (max_turns, reasoning_effort, approval_policy, features.web_search_request) reach the Codex CLI. This silently regresses:

  • max_turns – the CLI may run indefinitely.
  • reasoning_effort – the model ignores the configured effort level; calculateReasoningTimeout (line 829) computes a correct timeout, but the effort itself is never passed to the CLI.
  • features.web_search_request – web search is always disabled regardless of settings.

The entire overrides build-up (lines 769–790) plus the buildConfigOverrides call are effectively dead code in the current state. Either restore the spread into args or remove the dead code entirely if the CLI no longer accepts --config flags.

🐛 Proposed fix (restore config args)
-      buildConfigOverrides(overrides);
-      const preExecArgs: string[] = [];
+      const configArgs = buildConfigOverrides(overrides);
+      const preExecArgs: string[] = [...configArgs];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/providers/codex-provider.ts` around lines 769 - 812, The
buildConfigOverrides(overrides) return value is unused so the CLI never receives
the generated --config flags; capture its returned string[] and include (spread)
it into the args array before executing the Codex CLI. Specifically, after
building overrides (the overrides array) call buildConfigOverrides(overrides),
assign the result to a variable (e.g., configArgs) and insert/spread those
values into the args construction (with CODEX_EXEC_SUBCOMMAND,
CODEX_YOLO_FLAG..., ...preExecArgs, CODEX_MODEL_FLAG, options.model,
CODEX_JSON_FLAG, '-') so that max_turns, CODEX_REASONING_EFFORT_KEY,
approval_policy and features.web_search_request are passed to the CLI;
alternatively, if the CLI no longer accepts --config flags, remove the overrides
building block (overrides and buildConfigOverrides usage) and associated
constants to avoid dead code.
apps/server/src/services/recovery-service.ts (1)

297-329: ⚠️ Potential issue | 🟡 Minor

clearExecutionState is never called on the empty-list early return.

Line 298's return exits the method before reaching clearExecutionState on line 328. If reconcileAllFeatureStates() already moved all previously-running features to terminal states before resumeInterruptedFeatures is called, allInterruptedFeatures will be empty and the execution-state file will persist on disk indefinitely. Every subsequent call hits the same early return, so the file is never cleaned up.

🛠️ Proposed fix
       const allInterruptedFeatures = [...featuresWithContext, ...featuresWithoutContext];
-      if (allInterruptedFeatures.length === 0) return;
+      if (allInterruptedFeatures.length === 0) {
+        await this.clearExecutionState(projectPath);
+        return;
+      }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/services/recovery-service.ts` around lines 297 - 329, The
early return in resumeInterruptedFeatures exits when allInterruptedFeatures is
empty and never calls clearExecutionState, leaving the execution-state file on
disk; update resumeInterruptedFeatures so that when
allInterruptedFeatures.length === 0 it still awaits
this.clearExecutionState(projectPath) before returning (or move the
clearExecutionState call earlier, e.g., immediately after computing
allInterruptedFeatures and before the return), ensuring
clearExecutionState(projectPath) is always invoked even on the empty-list path;
keep references to the allInterruptedFeatures variable and clearExecutionState
method in your change so reviewers can easily locate the fix.
🧹 Nitpick comments (14)
apps/server/src/routes/app-spec/generate-features-from-spec.ts (1)

29-33: Remove the dead constant entirely rather than suppressing the lint warning with _.

The constant _CODEX_FEATURE_GENERATION_TIMEOUT_MS is never referenced anywhere in this file — the actual Codex timeout extension is handled by setting effectiveReasoningEffort = 'xhigh' at Line 206. The _ prefix only silences the linter; the dead code (including the JSDoc block) remains. Given the PR's stated goal of removing unused variables, deleting both the comment and the constant is the right call.

♻️ Proposed removal
-/**
- * Timeout for Codex models when generating features (5 minutes).
- * Codex models are slower and need more time to generate 50+ features.
- */
-const _CODEX_FEATURE_GENERATION_TIMEOUT_MS = 300000; // 5 minutes
-
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/routes/app-spec/generate-features-from-spec.ts` around lines
29 - 33, Remove the unused JSDoc and constant
_CODEX_FEATURE_GENERATION_TIMEOUT_MS from the file; locate the declaration of
the constant named _CODEX_FEATURE_GENERATION_TIMEOUT_MS and delete both its
comment block and the const line, leaving the rest of the file (including the
existing Codex handling via effectiveReasoningEffort = 'xhigh') unchanged.
apps/server/src/tests/cli-integration.test.ts (1)

273-278: Correct use of the _ prefix for the unused destructured key.

_provider follows the idiomatic TypeScript/ESLint convention for intentionally unused destructured parameters, cleanly suppressing the no-unused-vars warning without removing the structural destructure.

One pre-existing nit adjacent to this change: the expect(result.issues.length).toBeGreaterThan(0) assertion on line 275 is tautological — the enclosing if guard already confirms result.issues.length > 0. Consider simplifying to just the meaningful assertion:

♻️ Proposed simplification of the loop body
  Object.entries(results).forEach(([_provider, result]) => {
    if (!result.detected && result.issues.length > 0) {
-     expect(result.issues.length).toBeGreaterThan(0);
      expect(result.issues[0]).toBeTruthy();
    }
  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/tests/cli-integration.test.ts` around lines 273 - 278, The
loop currently uses the unused destructured key name _provider correctly, but
the inner assertion expect(result.issues.length).toBeGreaterThan(0) is
tautological because the if guard already checks result.issues.length > 0;
remove that redundant assertion and keep only the meaningful check (e.g., assert
the first issue is truthy) inside the for-each over Object.entries(results).
Maintain the destructured parameter name _provider and the variable result to
locate the loop and update its body accordingly.
apps/ui/src/components/views/board-view/components/kanban-card/card-actions.tsx (2)

129-166: Duplicated Logs + Stop button JSX across branches.

The Logs button (lines 130–152) and Stop button (lines 154–166) are near-identical copies of lines 80–102 and 104–117 in the isCurrentAutoTask block above. Consider extracting small helper components (e.g., LogsButton and StopButton) to reduce the duplication and keep future styling/behavior changes in sync.

♻️ Sketch of extracted helpers
+// At module level or above the component
+function LogsButton({ featureId, shortcutKey, onViewOutput }: {
+  featureId: string; shortcutKey?: string; onViewOutput: () => void;
+}) {
+  return (
+    <Button
+      variant="secondary"
+      size="sm"
+      className="flex-1 h-7 text-[11px]"
+      onClick={(e) => { e.stopPropagation(); onViewOutput(); }}
+      onPointerDown={(e) => e.stopPropagation()}
+      data-testid={`view-output-${featureId}`}
+    >
+      <FileText className="w-3 h-3 mr-1 shrink-0" />
+      <span className="truncate">Logs</span>
+      {shortcutKey && (
+        <span className="ml-1.5 px-1 py-0.5 text-[9px] font-mono rounded bg-foreground/10"
+          data-testid={`shortcut-key-${featureId}`}>
+          {shortcutKey}
+        </span>
+      )}
+    </Button>
+  );
+}
+
+function StopButton({ featureId, onForceStop }: {
+  featureId: string; onForceStop: () => void;
+}) {
+  return (
+    <Button
+      variant="destructive"
+      size="sm"
+      className="h-7 text-[11px] px-2 shrink-0"
+      onClick={(e) => { e.stopPropagation(); onForceStop(); }}
+      onPointerDown={(e) => e.stopPropagation()}
+      data-testid={`force-stop-${featureId}`}
+    >
+      <StopCircle className="w-3 h-3" />
+    </Button>
+  );
+}

Then replace the four occurrences (two in isCurrentAutoTask, two in the race-window branch) with <LogsButton … /> and <StopButton … />.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/ui/src/components/views/board-view/components/kanban-card/card-actions.tsx`
around lines 129 - 166, Duplicate JSX for the Logs and Stop buttons appears in
both the isCurrentAutoTask branch and the alternate branch; extract these into
two small stateless components (e.g., LogsButton and StopButton) that accept the
needed props (onViewOutput, onForceStop, shortcutKey, feature.id) and preserve
the existing handlers (e.stopPropagation in onClick and onPointerDown), testids
(`view-output-${feature.id}`, `shortcut-key-${feature.id}`,
`force-stop-${feature.id}`) and classes; then replace the four duplicated blocks
with <LogsButton .../> and <StopButton .../> calls so styling/behavior stays
centralized and future updates are applied in one place (ensure prop names match
the existing onViewOutput/onForceStop handlers).

1-1: @ts-nocheck suppresses all type checking for this file.

This was pre-existing, but given that the file is growing in complexity with new conditional branches, it would be worth revisiting whether proper discriminated-union typing on the props (e.g., using feature.status as the discriminant) could replace this blanket suppression. The comment mentions "optional callback prop typing with feature status narrowing" — TypeScript's narrowing and overloaded prop interfaces can handle this well.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/ui/src/components/views/board-view/components/kanban-card/card-actions.tsx`
at line 1, The file currently uses a blanket "@ts-nocheck" which hides real
typing issues; remove that and replace it with a discriminated-union props type
that uses feature.status as the discriminant so TypeScript can narrow allowed
optional callbacks per state. Update the CardActions component props (e.g., the
Props / CardActionsProps interface and any Feature type) to declare unions like
{ feature: { status: "open"; ... }; onOpenAction?: (...) => void } | { feature:
{ status: "closed"; ... }; onCloseAction?: (...) => void } (adjust callback
names to match existing callbacks in this file) so inside CardActions you can
switch/if on feature.status and call only the callbacks valid for that branch
without type errors; also update any usages to satisfy the new prop shape.
Ensure you remove "@ts-nocheck" after types compile.
apps/server/src/routes/features/routes/generate-title.ts (1)

17-20: Remove the unused projectPath field from GenerateTitleRequestBody.

After line 37 stopped destructuring projectPath, the field remains dead code in the interface. Removing it prevents misleading API consumers that the field is consumed.

Cleanup
 interface GenerateTitleRequestBody {
   description: string;
-  projectPath?: string;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/routes/features/routes/generate-title.ts` around lines 17 -
20, The interface GenerateTitleRequestBody currently declares an unused optional
field projectPath; remove the projectPath property from the
GenerateTitleRequestBody interface so the type only contains description:
string, and search for/remediate any remaining references to
GenerateTitleRequestBody.projectPath (e.g., in request destructuring or docs) to
avoid stale or misleading API typings.
apps/server/src/routes/models/routes/providers.ts (1)

9-34: Consider delegating to a service and emitting provider-check events.

Two guideline points apply here:

  1. The route handler calls ProviderFactory.checkAllProviders() directly rather than delegating to a dedicated service in services/.
  2. No event is emitted via createEventEmitter() to stream the result to the frontend via WebSocket.

Both are low-impact for a read-only status check, but they diverge from the project conventions.

As per coding guidelines: "Server business logic should be organized into services in the services/ directory, with Express route handlers in routes/ that delegate to services" and "All server operations should emit events using createEventEmitter() from lib/events.ts that stream to the frontend via WebSocket."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/routes/models/routes/providers.ts` around lines 9 - 34, The
route handler createProvidersHandler should delegate provider-check logic to a
new service function (e.g., services/providersService.getAllProvidersStatus)
instead of calling ProviderFactory.checkAllProviders() directly; move the call
to ProviderFactory.checkAllProviders() into that service, build the providers
object there, and have the service emit the result via createEventEmitter()
(from lib/events.ts) so the status is streamed to the frontend; then update
createProvidersHandler to call getAllProvidersStatus(), return its result in the
JSON response, and handle errors as before.
apps/server/src/lib/cli-detection.ts (1)

24-28: Remove dead API surface: includeWsl and wslDistribution from CliDetectionOptions.

These fields are defined in the interface but never destructured or used in detectCli or detectAllCLis. No callers pass these options, and removing them will eliminate misleading API surface.

♻️ Proposed cleanup
 export interface CliDetectionOptions {
   timeout?: number;
-  includeWsl?: boolean;
-  wslDistribution?: string;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/lib/cli-detection.ts` around lines 24 - 28, The
CliDetectionOptions interface exposes unused properties includeWsl and
wslDistribution which are dead API; remove these two fields from the
CliDetectionOptions declaration and update any related docs/comments to reflect
the reduced shape, and verify there are no references in detectCli or
detectAllCLis (and remove any unused destructuring or parameter defaults there
if present) so the public API only includes timeout as used by the
implementation.
apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx (1)

115-123: Consider extracting isInExecutionState into a shared helper.

The isInExecutionState + isActivelyRunning computation is duplicated verbatim in both kanban-card.tsx and list-view/list-row.tsx. Extracting it into a shared utility (e.g., in @/lib/feature-utils.ts) would keep the guard logic in one place and make future status additions automatic for both views.

♻️ Suggested extraction
// `@/lib/feature-utils.ts`  (new or existing utils file)
+export function isFeatureInExecutionState(status: string): boolean {
+  return status === 'in_progress' || status.startsWith('pipeline_');
+}

Then in both components:

-const isInExecutionState =
-  feature.status === 'in_progress' ||
-  (typeof feature.status === 'string' && feature.status.startsWith('pipeline_'));
-const isActivelyRunning = !!isCurrentAutoTask && isInExecutionState;
+const isActivelyRunning = !!isCurrentAutoTask && isFeatureInExecutionState(feature.status);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx`
around lines 115 - 123, Extract the duplicated execution-state logic into a
single exported helper (e.g., isFeatureInExecutionState) and replace the inline
`isInExecutionState` checks in both the Kanban card and List row with a call to
that helper; specifically, create a function (exported from a shared utils
module) that accepts the feature status string and returns true for `status ===
'in_progress' || status.startsWith('pipeline_')`, then update the
`isInExecutionState` usage in the kanban-card component (where
`isActivelyRunning` is computed) and the corresponding `list-row` computation to
call this helper so the guard logic is centralized and reused.
apps/ui/src/components/views/board-view/components/list-view/row-actions.tsx (2)

489-499: Redundant handlers.onForceStop guard inside a block that already requires it.

The outer condition at line 464 already ensures handlers.onForceStop is truthy when entering this branch, making the inner {handlers.onForceStop && ...} check at line 489 unnecessary.

♻️ Suggested simplification
-              {handlers.onForceStop && (
-                <>
-                  <DropdownMenuSeparator />
-                  <MenuItem
-                    icon={StopCircle}
-                    label="Force Stop"
-                    onClick={withClose(handlers.onForceStop)}
-                    variant="destructive"
-                  />
-                </>
-              )}
+              <>
+                <DropdownMenuSeparator />
+                <MenuItem
+                  icon={StopCircle}
+                  label="Force Stop"
+                  onClick={withClose(handlers.onForceStop)}
+                  variant="destructive"
+                />
+              </>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/components/views/board-view/components/list-view/row-actions.tsx`
around lines 489 - 499, The inner guard `{handlers.onForceStop && (...)}` is
redundant because the outer branch already ensures `handlers.onForceStop` is
truthy; remove the inner conditional and directly render the Separator and
MenuItem (using StopCircle, MenuItem, DropdownMenuSeparator and
onClick={withClose(handlers.onForceStop)}) to simplify the JSX and avoid
duplicate checks.

503-507: Simplify the double-negation condition for clarity.

!(!feature.error && handlers.onForceStop) is logically equivalent to (feature.error || !handlers.onForceStop) (De Morgan's law) but harder to parse at a glance.

♻️ Suggested simplification
-          !(!feature.error && handlers.onForceStop) && (
+          (!!feature.error || !handlers.onForceStop) && (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/components/views/board-view/components/list-view/row-actions.tsx`
around lines 503 - 507, The conditional rendering uses a confusing
double-negation: replace the expression "!(!feature.error &&
handlers.onForceStop)" with the clearer equivalent "(feature.error ||
!handlers.onForceStop)" so the full condition becomes: !isCurrentAutoTask &&
feature.status === 'in_progress' && (feature.error || !handlers.onForceStop);
update the condition where it's used in row-actions.tsx (around the block
guarded by isCurrentAutoTask, feature.status, feature.error, and
handlers.onForceStop) to improve readability while preserving logic.
apps/server/src/providers/codex-provider.ts (1)

1020-1025: getCodexAuthIndicators() is called but its result is unused — dead await.

detectInstallation() now derives auth status exclusively from checkCodexAuthentication() (line 1041). The await getCodexAuthIndicators() on line 1023 is a pure read with no side effects; discarding its result makes the call a no-op. Remove it to eliminate unnecessary I/O on every detectInstallation invocation.

🛠️ Proposed fix
-    await getCodexAuthIndicators();
     const installed = !!cliPath;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/providers/codex-provider.ts` around lines 1020 - 1025, The
call to getCodexAuthIndicators() inside detectInstallation() is unused and
creates unnecessary I/O; remove the dead await call and let detectInstallation()
rely on hasApiKey and checkCodexAuthentication() for auth state instead (update
any docs/comments if present). Specifically, delete the await
getCodexAuthIndicators() invocation from the detectInstallation function and
ensure checkCodexAuthentication() (and hasApiKey) remain the source of
authentication status.
apps/server/src/services/zai-usage-service.ts (1)

349-383: Redundant clearTimeout inside tryfinally already covers it.

clearTimeout(timeoutId) is called at line 363 inside the try block and again unconditionally in the finally block at line 382. The finally call subsumes line 363; calling clearTimeout on an already-cleared timer is harmless but the inner call is unnecessary noise.

♻️ Suggested cleanup
         const response = await fetch(quotaUrl, { ... });
-        clearTimeout(timeoutId);

         if (!response.ok) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/services/zai-usage-service.ts` around lines 349 - 383, The
code calls clearTimeout(timeoutId) twice inside fetchUsageData: once inside the
inner try after await fetch and again in the finally block; remove the redundant
inner clearTimeout call (the finally block that clears timeoutId using the
AbortController/timeoutId already guarantees cleanup) so only the finally
retains clearTimeout(timeoutId) and the AbortController usage remains unchanged.
apps/ui/src/components/views/settings-view/api-keys/hooks/use-api-key-management.ts (1)

10-11: import appears after a non-import statement — move it to the top.

Line 11's import { getElectronAPI } is placed after the const logger = createLogger(...) statement on line 10. ES/TS imports are hoisted at runtime, but this violates import/first lint rules and is non-idiomatic. All imports should precede any executable module-level statements.

♻️ Proposed fix
+import { getElectronAPI } from '@/lib/electron';
+import type { ProviderConfigParams } from '@/config/api-providers';
+
 const logger = createLogger('ApiKeyManagement');
-import { getElectronAPI } from '@/lib/electron';
-import type { ProviderConfigParams } from '@/config/api-providers';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/ui/src/components/views/settings-view/api-keys/hooks/use-api-key-management.ts`
around lines 10 - 11, The module-level `import { getElectronAPI }` is placed
after the executable statement `const logger =
createLogger('ApiKeyManagement')`; move the `getElectronAPI` import (and any
other imports) above any executable code so all imports appear at the top of the
file, e.g. place `import { getElectronAPI } from '@/lib/electron';` before the
`const logger = createLogger('ApiKeyManagement');` line and group it with the
other imports to satisfy the `import/first` lint rule.
apps/server/src/services/gemini-usage-service.ts (1)

247-299: Array.map() used with external side-effects — consider forEach + separate return.

The map callback on line 260 both builds the returned GeminiQuotaBucket[] and mutates six external variables (overallLowestRemaining, constrainedModel, overallResetTime, flashLowestRemaining, etc.). Using map for side-effects is non-idiomatic and makes the data-flow harder to follow. A forEach for the tracking pass followed by a separate map (or combined in a reduce) would make intent clearer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/services/gemini-usage-service.ts` around lines 247 - 299, The
current use of apiBuckets.map in the Gemini quota computation mixes returning a
GeminiQuotaBucket[] with mutating external tracking variables
(overallLowestRemaining, constrainedModel, overallResetTime,
flashLowestRemaining, flashResetTime, hasFlashModels, pro equivalents), which is
non-idiomatic; refactor by first iterating over apiBuckets with a forEach (or a
single reduce) to compute the tracking variables (update overallLowestRemaining,
constrainedModel, overallResetTime, hasFlashModels, flashLowestRemaining,
flashResetTime, hasProModels, proLowestRemaining, proResetTime) and then create
the quotaBuckets array with a pure map that only returns { modelId,
remainingFraction, resetTime } (or do the whole thing in one reduce that returns
both the tracking summary and the array), updating code in
gemini-usage-service.ts around the apiBuckets.map usage and referencing the same
variable names used now.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/server/src/lib/permission-enforcer.ts`:
- Around line 15-20: The CursorToolCall interface is currently unexported but
used by exported functions checkToolCallPermission and logPermissionViolation;
either export CursorToolCall from this file now or (preferred) move its minimal
definition into `@automaker/types` and import it here (aligning with
CursorToolCallEvent already in that package). Update the local file to import
the moved CursorToolCall and adjust any references in checkToolCallPermission
and logPermissionViolation to use the shared type; keep the current all-optional
shape for compatibility and do not change the function logic.

In `@apps/server/src/providers/cursor-provider.ts`:
- Line 882: The call to getEffectivePermissions(...) inside executeQuery is dead
I/O because its return value is discarded; either remove the call and its import
(if permissions are not used) or use the returned permissions to enforce checks
before proceeding. If removing, delete the getEffectivePermissions call and
remove its import; if enforcing, assign const permissions = await
getEffectivePermissions(options.cwd || process.cwd()) and use permissions to
validate/deny file writes or shell command spawning in executeQuery (e.g., gate
the subprocess spawn or write operations based on permissions).

In `@apps/server/src/routes/setup/routes/verify-claude-auth.ts`:
- Line 154: The temp env override created by createTempEnvOverride(authEnv) is
never invoked (stored as _cleanupEnv) so process.env retains injected
credentials; ensure you call the returned cleanup function in the verification
flow's finally block: store the result of createTempEnvOverride(authEnv) (e.g.,
const cleanupEnv = createTempEnvOverride(authEnv)) and invoke cleanupEnv()
inside the existing finally block in verify-claude-auth (or wrap the try block
that uses the temp env), guaranteeing the restore runs regardless of success or
error to remove ANTHROPIC_API_KEY/ANTHROPIC_BASE_URL from process.env.

In `@apps/server/src/services/gemini-usage-service.ts`:
- Around line 449-470: The findGeminiBinaryPath implementation currently returns
directory paths because possiblePaths includes directory roots (e.g.,
path.join(os.homedir(), '.nvm', 'versions', 'node') and path.join(os.homedir(),
'.fnm', 'node-versions')), causing extractOAuthClientCredentials to compute the
wrong baseDir and fail OAuth discovery; remove those non-binary directory
entries from possiblePaths (or replace them with explicit versioned binary
paths) so only actual gemini executable paths are checked, or alternatively
change the existence check to confirm the path is a file/executable before
returning (update findGeminiBinaryPath and ensure extractOAuthClientCredentials
still derives baseDir from a confirmed binary path).

In `@apps/server/src/services/zai-usage-service.ts`:
- Around line 189-230: The methods configure, verifyApiKey and fetchUsageData
currently call createEventEmitter() locally which produces a new emitter with no
subscribers, so emitter.emit(...) are no-ops; modify the ZaiUsageService to
accept a shared TypedEventBus (or the shared emitter) via constructor injection
(e.g., constructor(private eventBus: TypedEventBus) {}) and replace local
createEventEmitter() usages with this.eventBus.emit(...) (referencing configure,
verifyApiKey, fetchUsageData and any other methods using createEventEmitter());
ensure the constructor parameter is used throughout and remove the local
createEventEmitter() calls so emitted 'notification:created' events are
delivered to subscribers.
- Around line 172-175: getApiHost() unconditionally prefixes Z_AI_API_HOST with
"https://" which yields double-protocol strings if the env var already contains
a scheme; change the logic so when process.env.Z_AI_API_HOST is present you
first check if it startsWith('http') and return it as-is, otherwise prepend
'https://'; apply the identical pattern to the inline usage of
process.env.Z_AI_API_HOST in verifyApiKey() so both functions (getApiHost and
verifyApiKey) consistently handle full URLs and bare hostnames.

In `@apps/ui/src/components/views/board-view/mobile-usage-bar.tsx`:
- Around line 237-251: fetchZaiUsage currently calls setZaiUsage(data) without a
timestamp so zaiUsageLastUpdated may never be set; mirror fetchGeminiUsage's
behavior by passing a timestamp to the store setter (e.g., call
setZaiUsage(data, Date.now())), and update the setZaiUsage type/signature where
it’s defined to accept the optional/required timestamp (similar to
setGeminiUsage) so the stale-check using zaiUsageLastUpdated works and z.ai
usage is cached for the 2-minute window.

In
`@apps/ui/src/components/views/settings-view/api-keys/hooks/use-api-key-management.ts`:
- Around line 228-251: The code calls getElectronAPI() without guarding for a
null return, so api.zai?.verify can throw; update the verify flow in the
function that contains getElectronAPI() (the block using api.zai?.verify and
calling setZaiTestResult/setTestingZaiConnection) to first check api is present
(e.g., if (!api || !api.zai || typeof api.zai.verify !== 'function') ), and if
missing setZaiTestResult to a clear error about the desktop API not being
available and skip calling verify; keep the existing catch/finally behavior but
ensure you return early when the API is absent to avoid the TypeError.

---

Outside diff comments:
In `@apps/server/src/providers/codex-provider.ts`:
- Around line 769-812: The buildConfigOverrides(overrides) return value is
unused so the CLI never receives the generated --config flags; capture its
returned string[] and include (spread) it into the args array before executing
the Codex CLI. Specifically, after building overrides (the overrides array) call
buildConfigOverrides(overrides), assign the result to a variable (e.g.,
configArgs) and insert/spread those values into the args construction (with
CODEX_EXEC_SUBCOMMAND, CODEX_YOLO_FLAG..., ...preExecArgs, CODEX_MODEL_FLAG,
options.model, CODEX_JSON_FLAG, '-') so that max_turns,
CODEX_REASONING_EFFORT_KEY, approval_policy and features.web_search_request are
passed to the CLI; alternatively, if the CLI no longer accepts --config flags,
remove the overrides building block (overrides and buildConfigOverrides usage)
and associated constants to avoid dead code.

In `@apps/server/src/routes/app-spec/common.ts`:
- Around line 50-55: getRunningProjectPath currently checks the truthiness of
the map value (RunningGeneration) so it always returns the first map entry;
update it to inspect the actual running flag (e.g., running.isRunning) when
iterating runningProjects so it only returns the path for entries where
RunningGeneration.isRunning is true; ensure you reference the RunningGeneration
shape and preserve behavior of setRunningState (which may delete entries) so
this becomes a correct explicit check rather than relying on that invariant.

In `@apps/server/src/routes/models/routes/providers.ts`:
- Around line 15-27: The providers response currently only returns "anthropic"
and "cursor" from the statuses map; update the providers Record in
apps/server/src/routes/models/routes/providers.ts (the providers variable
created from checkAllProviders()'s statuses) to include all registered providers
returned by checkAllProviders() — e.g., claude, cursor, codex, opencode, gemini,
copilot (and any others your registry returns) with appropriate fields
(available, version, path, method, authenticated and hasApiKey where relevant);
also correct the mapping for "anthropic" if it mistakenly reads statuses.claude
(ensure anthropic reads statuses.anthropic) so the frontend can discover all
providers via the /providers endpoint.

In `@apps/server/src/services/recovery-service.ts`:
- Around line 297-329: The early return in resumeInterruptedFeatures exits when
allInterruptedFeatures is empty and never calls clearExecutionState, leaving the
execution-state file on disk; update resumeInterruptedFeatures so that when
allInterruptedFeatures.length === 0 it still awaits
this.clearExecutionState(projectPath) before returning (or move the
clearExecutionState call earlier, e.g., immediately after computing
allInterruptedFeatures and before the return), ensuring
clearExecutionState(projectPath) is always invoked even on the empty-list path;
keep references to the allInterruptedFeatures variable and clearExecutionState
method in your change so reviewers can easily locate the fix.

---

Duplicate comments:
In `@apps/ui/src/components/views/board-view/mobile-usage-bar.tsx`:
- Around line 38-59: formatResetTime now correctly guards against past
timestamps by returning 'Resetting soon' when diff <= 0; mark the earlier review
thread as resolved and remove any duplicate/commented-out legacy implementations
related to formatResetTime to avoid confusion. Locate the helper function
formatResetTime in mobile-usage-bar.tsx and delete any redundant duplicate code
blocks or commented alternatives, then resolve the review conversation so only
the updated implementation remains.
- Around line 188-191: The code currently calls useAppStore() four times to pull
claudeUsage, codexUsage, zaiUsage and geminiUsage (and their
LastUpdated/setters); consolidate these into a single destructuring call to
useAppStore() to return { claudeUsage, claudeUsageLastUpdated, setClaudeUsage,
codexUsage, codexUsageLastUpdated, setCodexUsage, zaiUsage, zaiUsageLastUpdated,
setZaiUsage, geminiUsage, geminiUsageLastUpdated, setGeminiUsage } so the
component subscribes once and avoids duplicate hooks/subscriptions; update the
references in mobile-usage-bar.tsx to use that single destructured object and
remove the multiple useAppStore() calls.

In
`@apps/ui/src/components/views/settings-view/api-keys/hooks/use-api-key-management.ts`:
- Around line 284-303: The failure and catch branches call setApiKeys with zai:
zaiKey which persists a z.ai key locally even though api.zai.configure() failed;
change those branches (where result.success is false and in the catch) to avoid
persisting the failed zaiKey by either omitting zai from the payload and using
the existing apiKeys.zai value or explicitly setting zai: '' (depending on
desired UX), i.e., update the setApiKeys calls inside the error branch and catch
block so they do not write zai: zaiKey and instead reference apiKeys.zai or ''
while leaving anthropic/google/openai as-is; also keep or improve the
logger.error calls around api.zai.configure for clarity.

---

Nitpick comments:
In `@apps/server/src/lib/cli-detection.ts`:
- Around line 24-28: The CliDetectionOptions interface exposes unused properties
includeWsl and wslDistribution which are dead API; remove these two fields from
the CliDetectionOptions declaration and update any related docs/comments to
reflect the reduced shape, and verify there are no references in detectCli or
detectAllCLis (and remove any unused destructuring or parameter defaults there
if present) so the public API only includes timeout as used by the
implementation.

In `@apps/server/src/providers/codex-provider.ts`:
- Around line 1020-1025: The call to getCodexAuthIndicators() inside
detectInstallation() is unused and creates unnecessary I/O; remove the dead
await call and let detectInstallation() rely on hasApiKey and
checkCodexAuthentication() for auth state instead (update any docs/comments if
present). Specifically, delete the await getCodexAuthIndicators() invocation
from the detectInstallation function and ensure checkCodexAuthentication() (and
hasApiKey) remain the source of authentication status.

In `@apps/server/src/routes/app-spec/generate-features-from-spec.ts`:
- Around line 29-33: Remove the unused JSDoc and constant
_CODEX_FEATURE_GENERATION_TIMEOUT_MS from the file; locate the declaration of
the constant named _CODEX_FEATURE_GENERATION_TIMEOUT_MS and delete both its
comment block and the const line, leaving the rest of the file (including the
existing Codex handling via effectiveReasoningEffort = 'xhigh') unchanged.

In `@apps/server/src/routes/features/routes/generate-title.ts`:
- Around line 17-20: The interface GenerateTitleRequestBody currently declares
an unused optional field projectPath; remove the projectPath property from the
GenerateTitleRequestBody interface so the type only contains description:
string, and search for/remediate any remaining references to
GenerateTitleRequestBody.projectPath (e.g., in request destructuring or docs) to
avoid stale or misleading API typings.

In `@apps/server/src/routes/models/routes/providers.ts`:
- Around line 9-34: The route handler createProvidersHandler should delegate
provider-check logic to a new service function (e.g.,
services/providersService.getAllProvidersStatus) instead of calling
ProviderFactory.checkAllProviders() directly; move the call to
ProviderFactory.checkAllProviders() into that service, build the providers
object there, and have the service emit the result via createEventEmitter()
(from lib/events.ts) so the status is streamed to the frontend; then update
createProvidersHandler to call getAllProvidersStatus(), return its result in the
JSON response, and handle errors as before.

In `@apps/server/src/services/gemini-usage-service.ts`:
- Around line 247-299: The current use of apiBuckets.map in the Gemini quota
computation mixes returning a GeminiQuotaBucket[] with mutating external
tracking variables (overallLowestRemaining, constrainedModel, overallResetTime,
flashLowestRemaining, flashResetTime, hasFlashModels, pro equivalents), which is
non-idiomatic; refactor by first iterating over apiBuckets with a forEach (or a
single reduce) to compute the tracking variables (update overallLowestRemaining,
constrainedModel, overallResetTime, hasFlashModels, flashLowestRemaining,
flashResetTime, hasProModels, proLowestRemaining, proResetTime) and then create
the quotaBuckets array with a pure map that only returns { modelId,
remainingFraction, resetTime } (or do the whole thing in one reduce that returns
both the tracking summary and the array), updating code in
gemini-usage-service.ts around the apiBuckets.map usage and referencing the same
variable names used now.

In `@apps/server/src/services/zai-usage-service.ts`:
- Around line 349-383: The code calls clearTimeout(timeoutId) twice inside
fetchUsageData: once inside the inner try after await fetch and again in the
finally block; remove the redundant inner clearTimeout call (the finally block
that clears timeoutId using the AbortController/timeoutId already guarantees
cleanup) so only the finally retains clearTimeout(timeoutId) and the
AbortController usage remains unchanged.

In `@apps/server/src/tests/cli-integration.test.ts`:
- Around line 273-278: The loop currently uses the unused destructured key name
_provider correctly, but the inner assertion
expect(result.issues.length).toBeGreaterThan(0) is tautological because the if
guard already checks result.issues.length > 0; remove that redundant assertion
and keep only the meaningful check (e.g., assert the first issue is truthy)
inside the for-each over Object.entries(results). Maintain the destructured
parameter name _provider and the variable result to locate the loop and update
its body accordingly.

In
`@apps/ui/src/components/views/board-view/components/kanban-card/card-actions.tsx`:
- Around line 129-166: Duplicate JSX for the Logs and Stop buttons appears in
both the isCurrentAutoTask branch and the alternate branch; extract these into
two small stateless components (e.g., LogsButton and StopButton) that accept the
needed props (onViewOutput, onForceStop, shortcutKey, feature.id) and preserve
the existing handlers (e.stopPropagation in onClick and onPointerDown), testids
(`view-output-${feature.id}`, `shortcut-key-${feature.id}`,
`force-stop-${feature.id}`) and classes; then replace the four duplicated blocks
with <LogsButton .../> and <StopButton .../> calls so styling/behavior stays
centralized and future updates are applied in one place (ensure prop names match
the existing onViewOutput/onForceStop handlers).
- Line 1: The file currently uses a blanket "@ts-nocheck" which hides real
typing issues; remove that and replace it with a discriminated-union props type
that uses feature.status as the discriminant so TypeScript can narrow allowed
optional callbacks per state. Update the CardActions component props (e.g., the
Props / CardActionsProps interface and any Feature type) to declare unions like
{ feature: { status: "open"; ... }; onOpenAction?: (...) => void } | { feature:
{ status: "closed"; ... }; onCloseAction?: (...) => void } (adjust callback
names to match existing callbacks in this file) so inside CardActions you can
switch/if on feature.status and call only the callbacks valid for that branch
without type errors; also update any usages to satisfy the new prop shape.
Ensure you remove "@ts-nocheck" after types compile.

In
`@apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx`:
- Around line 115-123: Extract the duplicated execution-state logic into a
single exported helper (e.g., isFeatureInExecutionState) and replace the inline
`isInExecutionState` checks in both the Kanban card and List row with a call to
that helper; specifically, create a function (exported from a shared utils
module) that accepts the feature status string and returns true for `status ===
'in_progress' || status.startsWith('pipeline_')`, then update the
`isInExecutionState` usage in the kanban-card component (where
`isActivelyRunning` is computed) and the corresponding `list-row` computation to
call this helper so the guard logic is centralized and reused.

In
`@apps/ui/src/components/views/board-view/components/list-view/row-actions.tsx`:
- Around line 489-499: The inner guard `{handlers.onForceStop && (...)}` is
redundant because the outer branch already ensures `handlers.onForceStop` is
truthy; remove the inner conditional and directly render the Separator and
MenuItem (using StopCircle, MenuItem, DropdownMenuSeparator and
onClick={withClose(handlers.onForceStop)}) to simplify the JSX and avoid
duplicate checks.
- Around line 503-507: The conditional rendering uses a confusing
double-negation: replace the expression "!(!feature.error &&
handlers.onForceStop)" with the clearer equivalent "(feature.error ||
!handlers.onForceStop)" so the full condition becomes: !isCurrentAutoTask &&
feature.status === 'in_progress' && (feature.error || !handlers.onForceStop);
update the condition where it's used in row-actions.tsx (around the block
guarded by isCurrentAutoTask, feature.status, feature.error, and
handlers.onForceStop) to improve readability while preserving logic.

In
`@apps/ui/src/components/views/settings-view/api-keys/hooks/use-api-key-management.ts`:
- Around line 10-11: The module-level `import { getElectronAPI }` is placed
after the executable statement `const logger =
createLogger('ApiKeyManagement')`; move the `getElectronAPI` import (and any
other imports) above any executable code so all imports appear at the top of the
file, e.g. place `import { getElectronAPI } from '@/lib/electron';` before the
`const logger = createLogger('ApiKeyManagement');` line and group it with the
other imports to satisfy the `import/first` lint rule.

Comment on lines +15 to +20
/** Minimal shape of a Cursor tool call used for permission checking */
interface CursorToolCall {
shellToolCall?: { args?: { command: string } };
readToolCall?: { args?: { path: string } };
writeToolCall?: { args?: { path: string } };
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for CursorToolCall and related field names across the codebase
rg -n "CursorToolCall|shellToolCall|readToolCall|writeToolCall" --type ts -C 1

Repository: AutoMaker-Org/automaker

Length of output: 9614


🏁 Script executed:

#!/bin/bash
# Check the structure of `@automaker/types` package and its exports
find . -path "*/packages/types/*" -name "*.ts" -o -path "*/automaker/types/*" -name "*.ts" | head -20

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Look at the permission-enforcer.ts file to verify the current state
cat -n apps/server/src/lib/permission-enforcer.ts | head -50

Repository: AutoMaker-Org/automaker

Length of output: 1872


🏁 Script executed:

#!/bin/bash
# Check for export statements in permission-enforcer.ts
rg -n "^export" apps/server/src/lib/permission-enforcer.ts

Repository: AutoMaker-Org/automaker

Length of output: 198


Export CursorToolCall or move it to @automaker/types

The CursorToolCall interface is used as a parameter type in two exported functions (checkToolCallPermission and logPermissionViolation) but is not itself exported. This creates an ergonomic gap: callers cannot explicitly annotate variables with the type without importing it, though TypeScript's structural typing allows passing compatible objects.

More importantly, per coding guidelines ("Core TypeScript definitions with no dependencies should be isolated in the automaker/types package"), this interface should be defined in @automaker/types rather than locally. While CursorToolCallEvent already exists in the types package, the minimal shape defined here is specific to permission checking and should be extracted there and imported.

Minimal fix: Export the interface in this file, then refactor to move it to @automaker/types and import it for consistency with project conventions.

The all-optional design (allowing all three sub-call properties simultaneously) is not ideal semantically—a discriminated union would prevent invalid states—but behavior is currently correct due to priority-order checks in the implementation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/lib/permission-enforcer.ts` around lines 15 - 20, The
CursorToolCall interface is currently unexported but used by exported functions
checkToolCallPermission and logPermissionViolation; either export CursorToolCall
from this file now or (preferred) move its minimal definition into
`@automaker/types` and import it here (aligning with CursorToolCallEvent already
in that package). Update the local file to import the moved CursorToolCall and
adjust any references in checkToolCallPermission and logPermissionViolation to
use the shared type; keep the current all-optional shape for compatibility and
do not change the function logic.


// Get effective permissions for this project
const effectivePermissions = await getEffectivePermissions(options.cwd || process.cwd());
await getEffectivePermissions(options.cwd || process.cwd());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Dead I/O — getEffectivePermissions result is discarded, making this call a no-op.

getEffectivePermissions performs up to two readFile operations and has no side effects. Calling it without consuming the return value adds latency to every executeQuery invocation with zero effect on behaviour.

The variable was removed as "unused", but that points to one of two problems:

  1. Permissions were never enforced — the call should be removed entirely.
  2. Enforcement logic was accidentally dropped — the permissions result should be used to gate operations (e.g., validate that file writes/shell commands are permitted before spawning the subprocess).
🐛 Option A — remove the dead call (if permissions are not yet implemented)
-    // Get effective permissions for this project
-    await getEffectivePermissions(options.cwd || process.cwd());
-
     // Debug: log raw events when AUTOMAKER_DEBUG_RAW_OUTPUT is enabled

Also remove the getEffectivePermissions import at line 34 if it becomes unused:

-import { getEffectivePermissions } from '../services/cursor-config-service.js';
🛡️ Option B — wire up permissions enforcement (if this is the intended behaviour)
-    await getEffectivePermissions(options.cwd || process.cwd());
+    const permissions = await getEffectivePermissions(options.cwd || process.cwd());
+    if (permissions) {
+      // TODO: enforce permissions before spawning subprocess
+      // e.g. reject write/shell tool calls when not allowed
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await getEffectivePermissions(options.cwd || process.cwd());
const permissions = await getEffectivePermissions(options.cwd || process.cwd());
if (permissions) {
// TODO: enforce permissions before spawning subprocess
// e.g. reject write/shell tool calls when not allowed
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/providers/cursor-provider.ts` at line 882, The call to
getEffectivePermissions(...) inside executeQuery is dead I/O because its return
value is discarded; either remove the call and its import (if permissions are
not used) or use the returned permissions to enforce checks before proceeding.
If removing, delete the getEffectivePermissions call and remove its import; if
enforcing, assign const permissions = await getEffectivePermissions(options.cwd
|| process.cwd()) and use permissions to validate/deny file writes or shell
command spawning in executeQuery (e.g., gate the subprocess spawn or write
operations based on permissions).


// Create temporary environment override for SDK call
const cleanupEnv = createTempEnvOverride(authEnv);
const _cleanupEnv = createTempEnvOverride(authEnv);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

_cleanupEnv is never called — process.env is permanently modified (credential leak).

createTempEnvOverride(authEnv) immediately calls Object.assign(process.env, authEnv) and returns a restore function. Since _cleanupEnv is prefixed with _ (suppressing the lint warning) but never invoked, process.env retains the injected credentials (ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL, etc.) for the lifetime of the Node.js process. Every subsequent request — including requests from other users — will run under those leaked credentials/endpoints.

The finally block at Line 314 must call the cleanup function.

🔒 Proposed fix
-        const _cleanupEnv = createTempEnvOverride(authEnv);
+        const cleanupEnv = createTempEnvOverride(authEnv);

And in the finally block:

       } finally {
         clearTimeout(timeoutId);
+        cleanupEnv();
         // Clean up the auth session
         AuthSessionManager.destroySession(sessionId);
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/routes/setup/routes/verify-claude-auth.ts` at line 154, The
temp env override created by createTempEnvOverride(authEnv) is never invoked
(stored as _cleanupEnv) so process.env retains injected credentials; ensure you
call the returned cleanup function in the verification flow's finally block:
store the result of createTempEnvOverride(authEnv) (e.g., const cleanupEnv =
createTempEnvOverride(authEnv)) and invoke cleanupEnv() inside the existing
finally block in verify-claude-auth (or wrap the try block that uses the temp
env), guaranteeing the restore runs regardless of success or error to remove
ANTHROPIC_API_KEY/ANTHROPIC_BASE_URL from process.env.

Comment on lines +449 to +470
// Check common installation paths
const possiblePaths = [
// npm global installs
path.join(os.homedir(), '.npm-global', 'bin', 'gemini'),
'/usr/local/bin/gemini',
'/usr/bin/gemini',
// Homebrew
'/opt/homebrew/bin/gemini',
'/usr/local/opt/gemini/bin/gemini',
// nvm/fnm node installs
path.join(os.homedir(), '.nvm', 'versions', 'node'),
path.join(os.homedir(), '.fnm', 'node-versions'),
// Windows
path.join(os.homedir(), 'AppData', 'Roaming', 'npm', 'gemini.cmd'),
path.join(os.homedir(), 'AppData', 'Roaming', 'npm', 'gemini'),
];

for (const p of possiblePaths) {
if (fs.existsSync(p)) {
return p;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Directory paths in possiblePaths will cause findGeminiBinaryPath to return a directory instead of a binary.

Lines 459–460 include ~/.nvm/versions/node and ~/.fnm/node-versions in possiblePaths. Both are directories, and fs.existsSync() returns true for any existing path regardless of type. On any machine with nvm or fnm installed (very common among Node.js developers), the function short-circuits and returns a directory. Downstream, extractOAuthClientCredentials computes baseDir = path.dirname(resolvedDirectory) — one level above the nvm versions root — and all possibleOAuth2Paths entries are constructed from the wrong base, silently preventing OAuth credential extraction and token refresh.

🐛 Proposed fix — remove the non-binary directory entries
     const possiblePaths = [
       // npm global installs
       path.join(os.homedir(), '.npm-global', 'bin', 'gemini'),
       '/usr/local/bin/gemini',
       '/usr/bin/gemini',
       // Homebrew
       '/opt/homebrew/bin/gemini',
       '/usr/local/opt/gemini/bin/gemini',
-      // nvm/fnm node installs
-      path.join(os.homedir(), '.nvm', 'versions', 'node'),
-      path.join(os.homedir(), '.fnm', 'node-versions'),
       // Windows
       path.join(os.homedir(), 'AppData', 'Roaming', 'npm', 'gemini.cmd'),
       path.join(os.homedir(), 'AppData', 'Roaming', 'npm', 'gemini'),
     ];

If nvm/fnm support is desired, enumerate specific versioned binary paths or rely solely on the which/where call at line 435 (which correctly resolves the active version).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/services/gemini-usage-service.ts` around lines 449 - 470, The
findGeminiBinaryPath implementation currently returns directory paths because
possiblePaths includes directory roots (e.g., path.join(os.homedir(), '.nvm',
'versions', 'node') and path.join(os.homedir(), '.fnm', 'node-versions')),
causing extractOAuthClientCredentials to compute the wrong baseDir and fail
OAuth discovery; remove those non-binary directory entries from possiblePaths
(or replace them with explicit versioned binary paths) so only actual gemini
executable paths are checked, or alternatively change the existence check to
confirm the path is a file/executable before returning (update
findGeminiBinaryPath and ensure extractOAuthClientCredentials still derives
baseDir from a confirmed binary path).

Comment on lines +172 to +175
getApiHost(): string {
// Priority: 1. Instance host, 2. Z_AI_API_HOST env, 3. Default
return process.env.Z_AI_API_HOST ? `https://${process.env.Z_AI_API_HOST}` : this.apiHost;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

getApiHost() prepends https:// unconditionally — double-prefix if env var contains a full URL.

If Z_AI_API_HOST is set to https://api.z.ai, getApiHost() returns https://https://api.z.ai. The same guard used in setApiHost() (host.startsWith('http')) is missing here. The same issue exists in verifyApiKey() at line 246.

🐛 Proposed fix
 getApiHost(): string {
-  return process.env.Z_AI_API_HOST ? `https://${process.env.Z_AI_API_HOST}` : this.apiHost;
+  const envHost = process.env.Z_AI_API_HOST;
+  if (envHost) {
+    return envHost.startsWith('http') ? envHost : `https://${envHost}`;
+  }
+  return this.apiHost;
 }

Apply the same fix in verifyApiKey() at line 246 where Z_AI_API_HOST is also used inline.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
getApiHost(): string {
// Priority: 1. Instance host, 2. Z_AI_API_HOST env, 3. Default
return process.env.Z_AI_API_HOST ? `https://${process.env.Z_AI_API_HOST}` : this.apiHost;
}
getApiHost(): string {
// Priority: 1. Instance host, 2. Z_AI_API_HOST env, 3. Default
const envHost = process.env.Z_AI_API_HOST;
if (envHost) {
return envHost.startsWith('http') ? envHost : `https://${envHost}`;
}
return this.apiHost;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/services/zai-usage-service.ts` around lines 172 - 175,
getApiHost() unconditionally prefixes Z_AI_API_HOST with "https://" which yields
double-protocol strings if the env var already contains a scheme; change the
logic so when process.env.Z_AI_API_HOST is present you first check if it
startsWith('http') and return it as-is, otherwise prepend 'https://'; apply the
identical pattern to the inline usage of process.env.Z_AI_API_HOST in
verifyApiKey() so both functions (getApiHost and verifyApiKey) consistently
handle full URLs and bare hostnames.

Comment on lines +189 to +230
async configure(
options: { apiToken?: string; apiHost?: string },
settingsService: SettingsService
): Promise<ConfigureResult> {
const emitter = createEventEmitter();

if (options.apiToken !== undefined) {
// Set in-memory token
this.setApiToken(options.apiToken || '');

// Persist to credentials
try {
await settingsService.updateCredentials({
apiKeys: { zai: options.apiToken || '' },
} as Parameters<typeof settingsService.updateCredentials>[0]);
logger.info('[configure] Saved z.ai API key to credentials');
} catch (persistError) {
logger.error('[configure] Failed to persist z.ai API key:', persistError);
}
}

if (options.apiHost) {
this.setApiHost(options.apiHost);
}

const result: ConfigureResult = {
success: true,
message: 'z.ai configuration updated',
isAvailable: this.isAvailable(),
};

emitter.emit('notification:created', {
type: 'zai.configured',
success: result.success,
isAvailable: result.isAvailable,
});

return result;
}

/**
* Verify an API key without storing it.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Local createEventEmitter() instances have no subscribers — all emit calls are no-ops.

Each method (configure, verifyApiKey, fetchUsageData) creates a brand-new EventEmitter via createEventEmitter(). As seen in lib/events.ts, a freshly created emitter holds an empty Set<EventCallback>. Since nothing subscribes to these local instances before the methods return, every emitter.emit(...) call silently discards the event.

The correct pattern — used by services like RecoveryService — is to inject a shared event bus (e.g., TypedEventBus) via the constructor and call emit on that shared instance:

// Constructor injection (mirrors RecoveryService pattern)
export class ZaiUsageService {
  constructor(private eventBus: TypedEventBus) {}

  async configure(...) {
    // ...
    this.eventBus.emit('notification:created', { type: 'zai.configured', ... });
  }
}

Without this, the UI can never receive z.ai lifecycle events over WebSocket.

Based on learnings: "All server operations should emit events using createEventEmitter() from lib/events.ts that stream to the frontend via WebSocket."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/services/zai-usage-service.ts` around lines 189 - 230, The
methods configure, verifyApiKey and fetchUsageData currently call
createEventEmitter() locally which produces a new emitter with no subscribers,
so emitter.emit(...) are no-ops; modify the ZaiUsageService to accept a shared
TypedEventBus (or the shared emitter) via constructor injection (e.g.,
constructor(private eventBus: TypedEventBus) {}) and replace local
createEventEmitter() usages with this.eventBus.emit(...) (referencing configure,
verifyApiKey, fetchUsageData and any other methods using createEventEmitter());
ensure the constructor parameter is used throughout and remove the local
createEventEmitter() calls so emitted 'notification:created' events are
delivered to subscribers.

Comment on lines +237 to +251
const fetchZaiUsage = useCallback(async () => {
setIsZaiLoading(true);
try {
const api = getElectronAPI();
if (!api.zai) return;
const data = await api.zai.getUsage();
if (!('error' in data)) {
setZaiUsage(data);
}
} catch {
// Silently fail - usage display is optional
} finally {
setIsZaiLoading(false);
}
}, [setZaiUsage]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

fetchZaiUsage doesn't pass a timestamp to setZaiUsagezaiUsageLastUpdated may never be set.

fetchGeminiUsage (line 260) correctly calls setGeminiUsage(data, Date.now()), updating geminiUsageLastUpdated and enabling the 2-minute stale window. fetchZaiUsage (line 244) calls setZaiUsage(data) with no timestamp.

If the setZaiUsage store implementation doesn't implicitly update zaiUsageLastUpdated, the stale check at line 201 will always be true and z.ai usage will be fetched on every component mount with no cache benefit. This also contradicts the symmetric treatment of all providers in the stale-check logic (lines 198–203).

Either pass Date.now() explicitly (requires updating the setZaiUsage type signature to match setGeminiUsage) or ensure the store setter implicitly sets zaiUsageLastUpdated.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/components/views/board-view/mobile-usage-bar.tsx` around lines
237 - 251, fetchZaiUsage currently calls setZaiUsage(data) without a timestamp
so zaiUsageLastUpdated may never be set; mirror fetchGeminiUsage's behavior by
passing a timestamp to the store setter (e.g., call setZaiUsage(data,
Date.now())), and update the setZaiUsage type/signature where it’s defined to
accept the optional/required timestamp (similar to setGeminiUsage) so the
stale-check using zaiUsageLastUpdated works and z.ai usage is cached for the
2-minute window.

Comment on lines +228 to +251
try {
const api = getElectronAPI();
// Use the verify endpoint to test the key without storing it
const response: VerifyResponse | undefined = await api.zai?.verify(zaiKey);

if (response?.success && response?.authenticated) {
setZaiTestResult({
success: true,
message: response.message || 'Connection successful! z.ai API responded.',
});
} else {
setZaiTestResult({
success: false,
message: response?.error || 'Failed to connect to z.ai API.',
});
}
} catch {
setZaiTestResult({
success: false,
message: 'Network error. Please check your connection.',
});
} finally {
setTestingZaiConnection(false);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

getElectronAPI() can return nullapi.zai?.verify will throw TypeError when it does.

Line 229 does not guard against a null return from getElectronAPI(). The optional chain api.zai?.verify(...) only protects against api.zai being absent, not against api itself being null. This is inconsistent with the pattern used at line 98 (if (api?.setup?.getApiKeys)), and the resulting TypeError would be silently swallowed as a misleading "Network error. Please check your connection." message.

🛡️ Proposed fix
     try {
       const api = getElectronAPI();
+      if (!api?.zai?.verify) {
+        setZaiTestResult({ success: false, message: 'z.ai verification is unavailable in this environment.' });
+        setTestingZaiConnection(false);
+        return;
+      }
       // Use the verify endpoint to test the key without storing it
-      const response: VerifyResponse | undefined = await api.zai?.verify(zaiKey);
+      const response: VerifyResponse | undefined = await api.zai.verify(zaiKey);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
const api = getElectronAPI();
// Use the verify endpoint to test the key without storing it
const response: VerifyResponse | undefined = await api.zai?.verify(zaiKey);
if (response?.success && response?.authenticated) {
setZaiTestResult({
success: true,
message: response.message || 'Connection successful! z.ai API responded.',
});
} else {
setZaiTestResult({
success: false,
message: response?.error || 'Failed to connect to z.ai API.',
});
}
} catch {
setZaiTestResult({
success: false,
message: 'Network error. Please check your connection.',
});
} finally {
setTestingZaiConnection(false);
}
try {
const api = getElectronAPI();
if (!api?.zai?.verify) {
setZaiTestResult({ success: false, message: 'z.ai verification is unavailable in this environment.' });
setTestingZaiConnection(false);
return;
}
// Use the verify endpoint to test the key without storing it
const response: VerifyResponse | undefined = await api.zai.verify(zaiKey);
if (response?.success && response?.authenticated) {
setZaiTestResult({
success: true,
message: response.message || 'Connection successful! z.ai API responded.',
});
} else {
setZaiTestResult({
success: false,
message: response?.error || 'Failed to connect to z.ai API.',
});
}
} catch {
setZaiTestResult({
success: false,
message: 'Network error. Please check your connection.',
});
} finally {
setTestingZaiConnection(false);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/ui/src/components/views/settings-view/api-keys/hooks/use-api-key-management.ts`
around lines 228 - 251, The code calls getElectronAPI() without guarding for a
null return, so api.zai?.verify can throw; update the verify flow in the
function that contains getElectronAPI() (the block using api.zai?.verify and
calling setZaiTestResult/setTestingZaiConnection) to first check api is present
(e.g., if (!api || !api.zai || typeof api.zai.verify !== 'function') ), and if
missing setZaiTestResult to a clear error about the desktop API not being
available and skip calling verify; keep the existing catch/finally behavior but
ensure you return early when the API is absent to avoid the TypeError.

@gsxdsm gsxdsm closed this Feb 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cleanup remove unused files in the codebase Do Not Merge Use this label if something should not be merged. Refactor A complete logic rewrite is requested or being performed for an issue. Testers-Requested Request for others to test an enhancement or bug fix/etc.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant